{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Linear transformations\n",
    "\n",
    "When working in regular vector spaces, a common tool is a linear transformation, typically in the form of a matrix.\n",
    "\n",
    "While geometric algebra already provides the rotors as a means of describing transformations (see [the CGA tutorial section](cga/index.ipynb#Operations)), there are types of linear transformation that are not suitable for this representation.\n",
    "\n",
    "This tutorial leans heavily on the explanation of linear transformations in <cite data-cite=\"ga4cs\">GA4CS</cite>, chapter 4. It explores the [clifford.transformations](../api/transformations.rst) submodule."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Vector transformations in linear algebra\n",
    "\n",
    "As a brief reminder, we can represent transforms in $\\mathbb{R}^3$ using the matrices in $\\mathbb{R}^{3 \\times 3}$:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "rot_and_scale_x = np.array([\n",
    "    [1, 0, 0],\n",
    "    [0, 1, -1],\n",
    "    [0, 1, 1],\n",
    "])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can read this as a table, where each column corresponds to a component of the input vector, and each row a component of the output:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def show_table(data, cols, rows):\n",
    "    # trick to get a nice looking table in a notebook\n",
    "    import pandas as pd; return pd.DataFrame(data, columns=cols, index=rows)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "show_table(rot_and_scale_x, [\"$\\mathit{in}_%s$\" % c for c in \"xyz\"], [\"$\\mathit{out}_%s$\" % c for c in \"xyz\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can apply it to some vectors using the `@` matrix multiply operator:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "v1 = np.array([1, 0, 0])\n",
    "v2 = np.array([0, 1, 0])\n",
    "v3 = np.array([0, 0, 1])\n",
    "\n",
    "(\n",
    "    rot_and_scale_x @ v1,\n",
    "    rot_and_scale_x @ v2,\n",
    "    rot_and_scale_x @ v3,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We say this transformation is linear because $f(a + b) = f(a) + f(b)$: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "assert np.array_equal(\n",
    "    rot_and_scale_x @ (2*v1 + 3*v2),\n",
    "    2 * (rot_and_scale_x @ v1) + 3 * (rot_and_scale_x @ v2)\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Multivector transformations in geometric algebra\n",
    "\n",
    "How would we go about applying `rot_and_scale_x` in a geometric algebra? Clearly we can apply it to vectors in the same way as before, which we can do by unpacking coefficients and repacking them:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from clifford.g3 import *\n",
    "\n",
    "v = 2*e1 + 3*e2\n",
    "v_trans = layout.MultiVector()\n",
    "v_trans[1,], v_trans[2,], v_trans[3,] = rot_and_scale_x @ [v[1,], v[2,], v[3,]]\n",
    "v_trans"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "However, in geometric algebra we don't only care about the vectors, we want to transform the the higher-order blades too. This can be done via an outermorphism, which extends $f(a)$ to $f(a \\wedge b) = f(a) \\wedge f(b)$. This is where the `clifford.transformations` submodule comes in handy:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from clifford import transformations\n",
    "\n",
    "rot_and_scale_x_ga = transformations.OutermorphismMatrix(rot_and_scale_x, layout)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To apply these transformations, we use the `()` operator, rather than `@`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "rot_and_scale_x_ga(e12)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# check it's an outermorphism\n",
    "rot_and_scale_x_ga(e1) ^ rot_and_scale_x_ga(e2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It shouldn't come as a surprise that applying the transformation to the psuedoscalar will tell us the determinant of our original matrix - the determinant tells us how a transformation scales volumes, and `layout.I` is a representation of the unit volume element!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "np.linalg.det(rot_and_scale_x), rot_and_scale_x_ga(layout.I)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Matrix representation\n",
    "\n",
    "Under the hood, clifford implements this using a matrix too - it's just now a matrix operating over all of the basis blades, not just over the vectors. We can see this by looking at the _private_ `_matrix` attribute:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "show_table(rot_and_scale_x_ga._matrix, [\"$\\mathit{in}_{%s}$\" % c for c in layout.names], [\"$\\mathit{out}_{%s}$\" % c for c in layout.names])"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
